#pragma once
template<class Type>
class DynArray
{
private:
	unsigned int Size, Capacity;
	Type * data;
	//Ctor
public:
	DynArray();
	//Dtor
	~DynArray();
	//Copy Ctor
	DynArray(const DynArray<Type>& that);
	//operator=
	DynArray<Type>& operator=(const DynArray<Type>& that);
	//operator[]
	Type& operator[](const unsigned int index);
	//const operator[]
	const Type& operator[](const unsigned int index) const;
	//Function : size
	unsigned int size() const;
	// Function : capacity
	unsigned int capacity() const;
	// Function : clear
	void clear();
	// Function : append
	void append(const Type& item);
	// Function : reserve
	void reserve(const unsigned int & newCap = 0);
	// Function : insert
	void insert(const Type val, const unsigned int index);
	// Function : insert x N
	void insert(const Type * val, const unsigned int n, const unsigned int index);
	// Function : remove
	void remove(const unsigned int index);
	// Function : remove x N
	void remove(const unsigned int index, const unsigned int n);


};

/////////////////////////////////////////////////////////////////////////////
// Function :	Constructor
// Notes : Constructs an empty array (Size 0 Capacity 0)
/////////////////////////////////////////////////////////////////////////////
template<class Type>
DynArray<Type>::DynArray()
{
	Size = 0;
	Capacity = 0;
	data = nullptr;
}

/////////////////////////////////////////////////////////////////////////////
// Function :	Destructor
// Notes : cleans up any dynamic memory
/////////////////////////////////////////////////////////////////////////////
template<class Type>
DynArray<Type>::~DynArray()
{
	clear();
}

/////////////////////////////////////////////////////////////////////////////
// Function :	Copy Constructor
/////////////////////////////////////////////////////////////////////////////
template<class Type>
DynArray<Type>::DynArray(const DynArray<Type>& that)
{
	Size = that.Size;
	Capacity = that.Capacity;
	if (Size > 0)
	{
		data = new Type[Capacity];
		for (decltype(Size) i = 0; i < Size; i++)
		{
			data[i] = that.data[i];
		}
	}
	else
		data = nullptr;
}

/////////////////////////////////////////////////////////////////////////////
// Function :	Assignment Operator
/////////////////////////////////////////////////////////////////////////////
template<class Type>
DynArray<Type>& DynArray<Type>::operator=(const DynArray<Type>& that)
{
	if (this != &that)
	{
		if (data)
			this->clear();
		Size = that.Size;
		Capacity = that.Capacity;
		if (Capacity > 0)
			data = new Type[Capacity];
		if (Size > 0)
		{
			for (decltype(Size) i = 0; i < Size; i++)
			{
				data[i] = that.data[i];
			}
		}
	}

	return *this;
}

/////////////////////////////////////////////////////////////////////////////
// Function : operator[]
// Parameters : index - the index to access
// Return : Type & - returns the array element at [index]
// Notes : performs no error checking. user should ensure index is 
//		valid with the size() method
/////////////////////////////////////////////////////////////////////////////

template<class Type>
Type& DynArray<Type>::operator[](const unsigned int index)
{
	return data[index];
}

/////////////////////////////////////////////////////////////////////////////
// Function : operator[]
// Parameters : index - the index to access
// Return : const Type & - returns the array element at [index]
// Notes : performs no error checking. user should ensure index is 
//		valid with the size() method
/////////////////////////////////////////////////////////////////////////////

template<class Type>
const Type& DynArray<Type>::operator[](const unsigned int index) const
{
	return data[index];
}

/////////////////////////////////////////////////////////////////////////////
// Function :	size
// Returns : int - returns the number of items being stored
// Notes : this function returns the number of items being stored, 
/////////////////////////////////////////////////////////////////////////////
template<class Type>
unsigned int DynArray<Type>::size() const
{
	return Size;
}

/////////////////////////////////////////////////////////////////////////////
// Function : capacity
// Returns : int - returns the number of items the array can store before 
//		the next resize
// Notes : this function returns the number of items the array can store, 
//		not the number of bytes
/////////////////////////////////////////////////////////////////////////////
template<class Type>
unsigned int DynArray<Type>::capacity() const
{
	return Capacity;
}

/////////////////////////////////////////////////////////////////////////////
// Function :	clear
// Notes : cleans up any dynamic memory and resets size and capacity to 0
/////////////////////////////////////////////////////////////////////////////

template<class Type>
void DynArray<Type>::clear()
{
	if (data)
		delete[] data;

	data = nullptr;
	Capacity = 0;
	Size = 0;
}

/////////////////////////////////////////////////////////////////////////////
// Function : append
// Parameters : item - the item to be appended to the next open spot
// Notes : this function will append the item to the next open spot. if 
//		no room exists, the array's capacity will be doubled and then 
//		the item will be added
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::append(const Type& item)
{
	if (Capacity == 0 || Size == Capacity)
	{
		reserve();
		data[Size] = item;
	}
	else
	{
		data[Size] = item;
	}
	Size++;
}

/////////////////////////////////////////////////////////////////////////////
// Function : reserve
// Parameters : newCap - the new capacity
// Notes : 	- default parameter - reserve more space in the array, based on 
//		the expansion rate (100%, 1 minimum).
//		- non-default parameter, expand to the specified capacity
//		- if newCap is LESS than the current capacity, do nothing. 
//		This function should NOT make the array smaller.
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::reserve(const unsigned int & newCap = 0)
{
	switch (Capacity)
	{
	case 0:
		if (!newCap)
		{
			data = new Type[1];
			Capacity = 1;
		}
		else
		{
			if (newCap > Capacity)
			{
				data = new Type[newCap];
				Capacity = newCap;
			}
			break;
		}
		break;
	default:
		if (!newCap)
		{
			decltype(Capacity) tempcap = Capacity * 2;
			decltype(Capacity) tempsize = Size;
			Type * temp = new Type[tempcap];
			for (decltype(Capacity) i = 0; i < tempsize; i++)
			{
				temp[i] = data[i];
			}
			this->clear();
			Capacity = tempcap;
			Size = tempsize;
			data = temp;
			break;
		}
		else if (newCap > Capacity)
		{
			int tempsize = Size;
			Type * temp = new Type[newCap];
			for (unsigned int i = 0; i < Size && Size > 0; i++)
			{
				temp[i] = data[i];
			}
			this->clear();
			Capacity = newCap;
			Size = tempsize;
			data = temp;
			break;
		}
		break;
	}
}

/////////////////////////////////////////////////////////////////////////////
// Function :	insert
// Parameters : val - the value to insert
//		   index - the index to insert at
// Notes : if the array is full, this function should expand the array at 
//		the default expansion rate (double the capacity, 1 minimum)
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::insert(const Type val, const unsigned int index)
{
	if (index < Capacity || index == 0)
		insert(&val, 1, index);
}


/////////////////////////////////////////////////////////////////////////////
// Function :	insert
// Parameters : val - the items to insert
//		   n - the number of items to insert
//		   index - the index to insert at
// Notes : if the array is full, this function should expand the array at 
//		the default expansion rate (double the capacity, 1 minimum) 
//		before inserting
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::insert(const Type * val, const unsigned int n, const unsigned int index)
{
	if ((index == 0 || index > 0) && (index == 0 || index < Capacity))
	{
		switch (Size)
		{
		case 0:
			for (unsigned int i = 0; i < n; i++)
				append(val[i]);
			break;
		default:
			while (Size + n > Capacity)
				reserve();
			for (decltype(size()) i = Capacity - 1; i > index; i--)
				data[i] = data[i - n];
			for (decltype(size()) i = 0; i < n; i++)
				data[index + i] = val[i];
			Size += n;
			break;
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Function :	remove
// Parameters : index - the index to remove from
// Notes : this function removes one item from the specified index
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::remove(const unsigned int index)
{
	remove(index, 1);
}

/////////////////////////////////////////////////////////////////////////////
// Function :	remove
// Parameters : index - the index to remove from
//		   n - the number of items to remove
// Notes : this function removes multiple items from the specified index
/////////////////////////////////////////////////////////////////////////////
template<class Type>
void DynArray<Type>::remove(const unsigned int index, const unsigned int n)
{
	if ((index == 0 || index > 0) && (index == 0 || index < Size) && n <= Size)
	{
		if (n == Size)
		{
			Size = 0;
			return;
		}
		else
		{
			for (decltype(size()) i = index; i < Size; i++)
			{
				data[i] = data[i + n];
			}
		}
		Size -= n;
	}
}